package bsh.utils.reflection.transform;

import java.util.HashMap;
import java.util.Map;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import bsh.CompilationState;
import bsh.dyn.DynamicObject;
import bsh.exception.CompilationAssertionFailed;
import bsh.exception.CompilationFailedException;
import bsh.meta.JavaClassWrapper;
import bsh.meta.JavaMethodDescriptor;
import bsh.meta.MethodDescriptor;
import bsh.meta.TypeTransformation;
import bsh.meta.TypeWrapper;

/**
 * {@link TypeTransformationFactory} para tranformacoes em runtime
 * 
 * @author takeshi
 */
public class DynamicTypeTransformationFactory implements TypeTransformationFactory {

    private static final MethodDescriptor CONVERTION_METHOD;

    private static final Map<Class<?>, MethodDescriptor> PRIMITIVE_CONVERTIONS = new HashMap<Class<?>, MethodDescriptor>();

    static {
        try {
            CONVERTION_METHOD = new JavaMethodDescriptor(DynamicObject.class.getDeclaredMethod("castTo", Class.class));
            associate("toInt", int.class);
            associate("toShort", short.class);
            associate("toByte", byte.class);
            associate("toChar", char.class);
            associate("toBoolean", boolean.class);
            associate("toLong", long.class);
            associate("toFloat", float.class);
            associate("toDouble", double.class);
        } catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    /**
     * Associa um metodo a um tipo
     * 
     * @param methodName String
     * @param type type
     */
    private static void associate(String methodName, Class<?> type) {
        try {
            JavaMethodDescriptor method = new JavaMethodDescriptor(DynamicObject.class.getDeclaredMethod(methodName));
            PRIMITIVE_CONVERTIONS.put(type, method);
        } catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    /**
     * {@inheritDoc}
     * 
     */
    public TypeTransformation getTypeTransformation(final TypeWrapper fromType, final TypeWrapper toType) {
        if (JavaClassWrapper.isPrimitive(toType)) {
            return new TypeTransformation() {

                public boolean acceptsType(TypeWrapper type) {
                    return JavaClassWrapper.wrapperFor(DynamicObject.class).equals(fromType);
                }

                public TypeWrapper toType(TypeWrapper fromType) {
                    return toType;
                }

                public TypeWrapper transformStackTop(MethodVisitor visitor, TypeWrapper fromType, CompilationState state)
                        throws CompilationFailedException {
                    Class<?> unwrapJavaClass = JavaClassWrapper.unwrapJavaClass(toType);
                    if (unwrapJavaClass == null) {
                        throw new CompilationAssertionFailed("not a primitive: " + unwrapJavaClass);
                    }
                    MethodDescriptor method = PRIMITIVE_CONVERTIONS.get(unwrapJavaClass);
                    if (method == null) {
                        throw new CompilationAssertionFailed("cannot convert to");
                    }
                    method.writeInvoke(visitor, state);
                    return toType;
                }
            };
        }
        return new TypeTransformation() {

            public boolean acceptsType(TypeWrapper type) {
                return JavaClassWrapper.wrapperFor(DynamicObject.class).equals(fromType);
            }

            public TypeWrapper toType(TypeWrapper fromType) {
                return toType;
            }

            public TypeWrapper transformStackTop(MethodVisitor visitor, TypeWrapper fromType, CompilationState state)
                    throws CompilationFailedException {
                visitor.visitLdcInsn(toType.toASMType());
                state.pushStack(Class.class);
                CONVERTION_METHOD.writeInvoke(visitor, state);
                visitor.visitTypeInsn(Opcodes.CHECKCAST, toType.internalName());
                state.popStack();
                state.pushStack(toType);

                return toType;
            }
        };
    }

    /**
     * {@inheritDoc}
     * 
     */
    public boolean isApplicable(TypeWrapper fromType, TypeWrapper toType) {
        return DynamicObject.class.equals(JavaClassWrapper.unwrapJavaClass(fromType));
    }

}
